﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Controls.Interfaces;
using System.Drawing;
using Silvermoon.Core;
using Silvermoon.OpenGL.Native;
using Silvermoon.Windows;
using Silvermoon.UI;
using Silvermoon.Utils;

namespace Silvermoon
{
    public abstract class Transformation
    {
        protected static float[] Identity
        {
            get { return new float[] { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }; }
        }


        public abstract void Apply(Size size, ref  Renderer.RenderData data);

        /// <summary>
        /// Adds a transformation after the current transformation.
        /// </summary>
        /// <param name="current">Current transformation.</param>
        /// <param name="add">Transformation to add to chain.</param>
        /// <returns>Transformation with both.</returns>
        public static Transformation operator +(Transformation current, Transformation add)
        {
            if (current == null) return add;
            MultiTransformation m = current as MultiTransformation;
            if (m == null)
            {
                m = new MultiTransformation();
                if (current != null) m.AddLast(current);
            }
            m.AddLast(add);
            return m;
        }

        public static Transformation operator +(Transformation root, IEnumerable<Transformation> transformations)
        {
            MultiTransformation m = root as MultiTransformation;
            if (m == null)
            {
                m = new MultiTransformation();
                if (root != null) m.AddLast(root);
            }
            foreach (Transformation t in transformations)
            {
                m.AddLast(t);
            }
            return m;
        }

        /// <summary>
        /// Adds a transformation at before the current transformation.
        /// </summary>
        /// <param name="current">Current transformation.</param>
        /// <param name="add">Transformation to add.</param>
        /// <returns>Transformation with both.</returns>
        public static Transformation operator |(Transformation current, Transformation add)
        {
            if (current == null) return add;
            MultiTransformation m = current as MultiTransformation;
            if (m == null)
            {
                m = new MultiTransformation();
                if (current != null) m.AddLast(current);
            }
            m.AddFirst(add);
            return m;

        }


        /// <summary>
        /// Removes a transformation.
        /// </summary>
        /// <param name="current">Current transformation.</param>
        /// <param name="remove">Transformation to remove.</param>
        /// <returns>Transformation without remove.</returns>
        public static Transformation operator -(Transformation current, Transformation remove)
        {
            MultiTransformation m = current as MultiTransformation;
            if (m != null)
            {
                m.Remove(remove);
                return m.IsEmpty ? null : m;
            }
            return current == remove ? null : current;
        }

        public static Transformation operator -(Transformation current, IEnumerable<Transformation> remove)
        {
            MultiTransformation m = current as MultiTransformation;
            if (m != null)
            {
                foreach (Transformation t in remove)
                {
                    m.Remove(t);
                }
                return m.IsEmpty ? null : m;
            }
            foreach (Transformation t in remove)
            {
                if (current == t) return null;
            }
            return current;
        }

        public static Transformation operator -(Transformation a, DblLinkedNode<Transformation> node)
        {
            MultiTransformation m = a as MultiTransformation;
            if (m != null)
            {
                m.Remove(node);
                return m.IsEmpty ? null : m;
            }
            else throw new NotSupportedException();
        }


        /// <summary>
        /// Adds the translation offset to the given rectangle, if available.
        /// </summary>
        /// <param name="rect">Rectangle to offset to the given translation.</param>
        public virtual void AdjustBounds(ref Rectangle rect)
        {
        }
    }


    /// <summary>
    /// Makes the control or shape not to be clipped within the outer bounds.
    /// </summary>
    public class UnclippedTransform : Transformation
    {
        public override void Apply(Size size, ref Renderer.RenderData data)
        {
            data.DisableClipping = true;
            gl.Disable(Capability.ScissorTest);
            gl.Disable(Capability.ScissorBox);
        }

        public static readonly UnclippedTransform Instance = new UnclippedTransform();
    }

    public class RotateTransform : Transformation
    {
        public float Angle;
        public RotateAxis Axis;
        public float OrigX;
        public float OrigY;

        public static readonly float Degree90 = 90F;
        public static readonly float Degree180 = 180F;
        public static readonly float Degree270 = 270F;
        public static readonly float Degree0 = 0F;

        /// <summary>
        /// Rotastes coords.
        /// </summary>
        /// <remarks>
        /// The view matrix positon of the control is currently set to 0/0 thus, to rotate the control arround it's center,
        /// the view matrix must first be translated by the half width and height of the control.
        /// </remarks>
        /// <param name="control">The control to rotate.</param>
        /// <param name="rotateTransform">The transformation info.</param>
        public override void Apply(Size size, ref Renderer.RenderData data)
        {
            float x = size.Width / 2f;
            float y = size.Height / 2f;

            x = (OrigX + 1f) * x;
            y = (OrigY + 1f) * y;

            gl.Translatef(x, y, 0);

            RotateAxis axis = Axis;
            float ax = axis == RotateAxis.XAxis ? 1F : 0F;
            float ay = axis == RotateAxis.YAxis ? 1F : 0F;
            float az = axis == RotateAxis.ZAxis ? 1F : 0F;
            gl.Rotatef(Angle, ax, ay, az);
            gl.Translatef(-x, -y, 0);

            if (Math.IEEERemainder(Angle, 360d) != 0d)
            {
                data.DisableClipping = true;
            }
        }
    }


    public enum RotateAxis
    {
        XAxis,
        YAxis,
        ZAxis
    }

    /// <summary>
    /// Scales from the center.
    /// </summary>
    public class ScaleTransform : Transformation, IScaleTransform
    {
        public float X { get; set; }
        public float Y { get; set; }
        public float Z { get; set; }

        public ScaleTransform()
            : base()
        {
            X = Y = Z = 1f;
        }

        public ScaleTransform(float scaleX, float scaleY, float scaleZ)
        {
            X = scaleX;
            Y = scaleY;
            Z = scaleZ;
        }

        /// <summary>
        /// Scales coords.
        /// </summary>
        /// <remarks>
        /// The view matrix positon of the control is currently set to 0/0 thus, to rotate the control arround it's center,
        /// the view matrix must first be translated by the half width and height of the control.
        /// </remarks>
        /// <param name="control">The control to rotate.</param>
        /// <param name="rotateTransform">The transformation info.</param>
        public override void Apply(Size size, ref Renderer.RenderData data)
        {
            float x = size.Width / 2f;
            float y = size.Height / 2f;
            gl.Translatef(x, y, 0);

            gl.Scalef(X, Y, Z);
            gl.Translatef(-x, -y, 0);

            data.X += (x * data.ScaleX * (1 - X));
            data.Y += (y * data.ScaleY * (1 - Y));
            data.ScaleX *= X;
            data.ScaleY *= Y;
        }

        public override int GetHashCode()
        {
            return X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            ScaleTransform x = obj as ScaleTransform;
            if (x == null) return false;
            return x.X == X && x.Y == Y && x.Z == Z;
        }

        public override void AdjustBounds(ref Rectangle rect)
        {
            if (X != 1f || Y != 1f)
            {
                int w = rect.Width;
                int h = rect.Height;
                rect.Width = (int)((float)w * this.X + 0.5f);
                rect.Height = (int)((float)h * this.Y + 0.5f);
                rect.X += (w - rect.Width) / 2;
                rect.Y += (h - rect.Height) / 2;
            }
            base.AdjustBounds(ref rect);
        }

        public static readonly ScaleTransform One = new ScaleTransform(1f, 1f, 1f);
        public static readonly ScaleTransform Zero2D = new ScaleTransform(0f, 0f, 1f);
    }

    /// <summary>
    /// Scales from top left or any other location.
    /// </summary>
    public class StretchTransform : Transformation, IScaleTransform
    {
        public float X { get; set; }
        public float Y { get; set; }
        public float Z { get; set; }

        /// <summary>
        /// XOffset for the point to scale relative to the with of the area. A value of 0 means from left, 0.5 from center and 1 from right.
        /// </summary>
        public float XOffset { get; set; }

        /// <summary>
        /// YOffset for the point to scale relative to the height of the area. A value of 0 means from top, 0.5 from center and 1 from bottom.
        /// </summary>
        public float YOffset { get; set; }

        public StretchTransform()
            : base()
        {
            X = Y = Z = 1f;
        }

        public StretchTransform(float scaleX, float scaleY, float scaleZ)
        {
            X = scaleX;
            Y = scaleY;
            Z = scaleZ;
        }

        public override void Apply(Size size, ref Renderer.RenderData data)
        {
            if (XOffset == -0f && YOffset == 0f)
            {
                gl.Scalef(X, Y, Z);
                data.ScaleX *= X;
                data.ScaleY *= Y;
            }
            else
            {
                float x = XOffset;
                float y = YOffset;
                gl.Translatef(x, y, 0);

                gl.Scalef(X, Y, Z);
                gl.Translatef(-x, -y, 0);

                data.X += (x * data.ScaleX * (1 - X));
                data.Y += (y * data.ScaleY * (1 - Y));
                data.ScaleX *= X;
                data.ScaleY *= Y;
            }
        }
    }



    public class ShearTransform : Transformation
    {
        public ShearTransform()
            : base()
        {
        }

        public ShearTransform(float x, float y, float z)
            : base()
        {
            this.X = x;
            this.Y = y;
            this.Z = z;
        }

        public float X { get; set; }
        public float Y { get; set; }
        public float Z { get; set; }

        public override void Apply(Size size, ref Renderer.RenderData data)
        {
            float[] matrix = Identity;
            matrix[2] = X;
            matrix[2] = Y;
            matrix[3] = Z;
            //gl.LoadMatrixf(matrix);
            gl.MultMatrixf(matrix);

            data.DisableClipping = true;

        }
    }


    public class TranslateTransform : Transformation
    {
        public float X { get; set; }
        public float Y { get; set; }
        public float Z { get; set; }

        public override void Apply(Size size, ref Renderer.RenderData data)
        {
            gl.Translatef(X, Y, Z);
            data.X += X * data.ScaleX;
            data.Y += Y * data.ScaleY;

            if (Z != 0f) data.DisableClipping = true;

        }

        public TranslateTransform()
            : base()
        { }

        public TranslateTransform(float x, float y, float z)
            : this()
        {
            X = x;
            Y = y;
            Z = z;
        }

        public override bool Equals(object obj)
        {
            TranslateTransform c = obj as TranslateTransform;
            if (c == null) return false;
            return c.X == X && c.Y == Y && c.Z == Z;
        }

        public override int GetHashCode()
        {
            return X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode();
        }

        public override void AdjustBounds(ref Rectangle rect)
        {
            rect.X += (int)X;
            rect.Y += (int)Y;
        }



    }

    public class OpacityTransform : Transformation
    {
        private int alpha = 255;


        public int Alpha { get { return alpha; } set { value = CheckRange(value); alpha = value; } }

        private int CheckRange(int value)
        {
            if (value < 0) return 0;
            if (value > 255) return 255;
            return value;
        }

        public override void Apply(Size size, ref Renderer.RenderData data)
        {
            data.Alpha = (data.Alpha * Alpha) / 255;
        }
    }

    public class DepthTransform : Transformation
    {
        public float Depth { get; set; }

        public override void Apply(Size size, ref Renderer.RenderData data)
        {
            int x = size.Width / 2;
            int y = size.Height / 2;
            gl.Translatef(x, y, 0);

            gl.Translatef(0f, 0f, Depth);
            gl.Translatef(-x, -y, 0);
        }

        public DepthTransform()
            : base()
        { }

        public DepthTransform(float depth)
            : this()
        {
            Depth = depth;
        }
    }
}


